
// Simple232Dlg.cpp : implementation file
//
#include "stdafx.h"
#include "Simple232.h"
#include "Simple232Dlg.h"
#include "afxdialogex.h"

#include "EnumSerial.h"

#pragma warning(disable:4996)

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

static HANDLE _g_handleCOM  = INVALID_HANDLE_VALUE;
static HANDLE _g_handleExit = INVALID_HANDLE_VALUE;
static CFont _g_UseFont;

#define WM_COM_RECEIVED_BYTE    (WM_USER + 100)
#define WM_COM_SEND_BYTE        (WM_USER + 101)
#define WM_UPDATE_COUNTER       (WM_USER + 102)

//if log overflow, oldest log will delete!
#define MAX_LOG_NUMBER 1000

static const char* K_PARITY_STRING[] = {
    "no parity", 
    "odd parity", "even parity", 
    "mark parity", "space parity",
};

static const int K_PARITY_ENUM[] = {
    NOPARITY,
    ODDPARITY, EVENPARITY,
    MARKPARITY, SPACEPARITY,
};

static UINT _thread_receiver(LPVOID pParam)
{
    DWORD dwErrorFlags = 0;
    COMSTAT ComStat;
    OVERLAPPED  overlappedRead;
    bool bexit = false;

    CWnd* dlg = (CWnd*)pParam;

    //Prepare Overlapped
    memset(&overlappedRead, 0, sizeof(OVERLAPPED));
    overlappedRead.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    HANDLE hevents[2] = {_g_handleExit, overlappedRead.hEvent};

    while (1) {
        //Wait for signal change on device
        if(!WaitCommEvent(_g_handleCOM, &dwErrorFlags, &overlappedRead)) {
            if(GetLastError() == ERROR_IO_PENDING) {
                DWORD rc = WaitForMultipleObjects(2, hevents, FALSE, INFINITE) - WAIT_OBJECT_0;
                if (rc == 0) {//received exit event.
                    break;
                }
            }
        }

        //Check result and retrieve data.
        if ((dwErrorFlags & EV_RXCHAR) == EV_RXCHAR) {
            ClearCommError(_g_handleCOM, &dwErrorFlags, &ComStat);
            DWORD dwLength = ComStat.cbInQue;
            
            if (dwLength > 0) {
                char buffer[100];
                DWORD dwReadBytes = 0;
                DWORD dwTotalReadBytes = 0;
                memset(buffer, 0, sizeof(buffer));
                int rc = ReadFile(_g_handleCOM, buffer, dwLength, &dwTotalReadBytes, &overlappedRead);
                
                if (!rc) {
                    if (GetLastError() == ERROR_IO_PENDING) {
                        while(!GetOverlappedResult(_g_handleCOM, &overlappedRead, &dwReadBytes, FALSE)) {
                            if (WaitForSingleObject(_g_handleExit, 10) == WAIT_OBJECT_0) {
                                bexit = true;
                                break;
                            } else if (GetLastError() == ERROR_IO_INCOMPLETE) {
                                dwTotalReadBytes += dwReadBytes;
                                continue;
                            } else {
                                CString msg;
                                msg.Format("Error1 0x%08x", GetLastError());
                                AfxMessageBox(msg);

                                bexit = true;
                                break;
                            }
                        }

                        if (bexit) {
                            break;
                        }
                    } else {
                        CString msg;
                        msg.Format("Error2 0x%08x", GetLastError());
                        AfxMessageBox(msg);
                        break;
                    }
                }

                for (int i = 0;i < (int)dwTotalReadBytes;i++) {
                    dlg->PostMessage(WM_COM_RECEIVED_BYTE, (WPARAM)buffer[i]);
                }
            }
        }
    }

    CloseHandle(overlappedRead.hEvent);

    return 0;
}

// CSimple232Dlg dialog




CSimple232Dlg::CSimple232Dlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CSimple232Dlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    overlappedWrite_.hEvent = INVALID_HANDLE_VALUE;
}

void CSimple232Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_COMBO_ID, m_combox_id_);
    DDX_Control(pDX, IDC_COMBO_BAUD, m_combox_baud_);
    DDX_Control(pDX, IDC_BUTTON_OPEN, m_button_open_);
    DDX_Control(pDX, IDC_BUTTON_CLOSE, m_button_close_);
    DDX_Control(pDX, IDC_LIST_OUTPUT, m_listctrl_output_);
    DDX_Control(pDX, IDC_EDIT_STATUS, m_edit_status_);
    DDX_Control(pDX, IDC_COMBO_BITS, m_combox_bits_);
    DDX_Control(pDX, IDC_COMBO_PARITY, m_combox_parity_);
    DDX_Control(pDX, IDC_COMBO_STOPBITS, m_combox_stopbits_);
}

BEGIN_MESSAGE_MAP(CSimple232Dlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
    ON_WM_DESTROY()
    ON_BN_CLICKED(IDC_BUTTON_OPEN, &CSimple232Dlg::OnBnClickedButtonOpen)
    ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CSimple232Dlg::OnBnClickedButtonClose)
    ON_MESSAGE(WM_COM_RECEIVED_BYTE, &CSimple232Dlg::OnComReceivedByte)
    ON_MESSAGE(WM_COM_SEND_BYTE, &CSimple232Dlg::OnComSendByte)
    ON_MESSAGE(WM_UPDATE_COUNTER, &CSimple232Dlg::OnUpdateCounter)
    ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CSimple232Dlg::OnBnClickedButtonClose)
END_MESSAGE_MAP()


// CSimple232Dlg message handlers

BOOL CSimple232Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here
    m_button_open_.EnableWindow(TRUE);
    m_button_close_.EnableWindow(FALSE);

    //Device list
    CArray<SSerInfo,SSerInfo&> asi;
    EnumSerialPorts(asi, FALSE/*include all*/);

    for (int i = 0; i < asi.GetSize(); i++) {
        CString strid;
        strid.Format("%s|%s", asi[i].strPortName, asi[i].strFriendlyName);
        m_combox_id_.InsertString(i, strid);
    }
    m_combox_id_.SetCurSel(0);

    //Baud rate list
    int baudrate[] = {9600, 19200, 38400, 115200};
    for (int i = 0;i < sizeof(baudrate)/sizeof(int);i++) {
        CString strbaud;
        strbaud.Format("%d", baudrate[i]);
        m_combox_baud_.InsertString(i, strbaud);
    }
    m_combox_baud_.SetCurSel(sizeof(baudrate)/sizeof(int)-1);

    //Data bits
    m_combox_bits_.InsertString(0, "5 bits");
    m_combox_bits_.InsertString(1, "6 bits");
    m_combox_bits_.InsertString(2, "7 bits");
    m_combox_bits_.InsertString(3, "8 bits");
    m_combox_bits_.SetCurSel(3);

    //Parity
    for (int i = 0;i < sizeof(K_PARITY_STRING)/sizeof(const char*);i++) {
        m_combox_parity_.InsertString(i, K_PARITY_STRING[i]);
    }
    m_combox_parity_.SetCurSel(0);

    //Stop bits
    m_combox_stopbits_.InsertString(0, "1 stop bits");
    m_combox_stopbits_.InsertString(1, "1.5 stop bits");
    m_combox_stopbits_.InsertString(2, "2 stop bits");
    m_combox_stopbits_.SetCurSel(0);

    //Init output buffer 
    CRect rect;
    m_listctrl_output_.GetClientRect(&rect);
    m_listctrl_output_.InsertColumn(0, "BUFFER", 0, int(rect.Width() * 0.95));

    DWORD dwStyle;
    dwStyle = m_listctrl_output_.GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    dwStyle |= LVS_EX_GRIDLINES;
    m_listctrl_output_.SetExtendedStyle(dwStyle);

    LOGFONT lf;   
    memset(&lf, 0, sizeof(LOGFONT));
    lf.lfHeight = 16;
    strcpy(lf.lfFaceName, "Courier New");
    _g_UseFont.CreateFontIndirect(&lf);
    m_listctrl_output_.SetFont(&_g_UseFont, TRUE);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CSimple232Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CSimple232Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

BOOL CSimple232Dlg::PreTranslateMessage(MSG* pMsg)
{
    if ((pMsg->message == WM_KEYDOWN) || (pMsg->message == WM_KEYUP)) {
        int c = '\0';
        switch (pMsg->wParam) {
        case VK_ESCAPE://valid 'escape', can exit program.
            return CDialog::PreTranslateMessage(pMsg);

        case VK_F1:
        case VK_F2:
        case VK_F3:
        case VK_F4:
        case VK_F5:
        case VK_F6:
        case VK_F7:
        case VK_F8:
        case VK_F9:
        case VK_F10:
        case VK_F11:
        case VK_F12:
        case VK_F13:
        case VK_F14:
        case VK_F15:
        case VK_F16:
        case VK_F17:
        case VK_F18:
        case VK_F19:
        case VK_F20:
        case VK_F21:
        case VK_F22:
        case VK_F23:
        case VK_F24:
            return TRUE;

        case VK_RETURN:
            c = '\n';
            break;

        case VK_OEM_1:
            c = (GetKeyState(VK_SHIFT))?':':';';
            break;
        case VK_OEM_PLUS:
            c = (GetKeyState(VK_SHIFT))?'+':'=';
            break;
        case VK_OEM_COMMA:
            c = (GetKeyState(VK_SHIFT))?'<':',';
            break;
        case VK_OEM_MINUS:
            c = (GetKeyState(VK_SHIFT))?'_':'-';
            break;
        case VK_OEM_PERIOD:
            c = (GetKeyState(VK_SHIFT))?'>':'.';
            break;
        case VK_OEM_2:
            c = (GetKeyState(VK_SHIFT))?'?':'/';
            break;
        case VK_OEM_3:
            c = (GetKeyState(VK_SHIFT))?'~':'`';
            break;
        case VK_OEM_4:
            c = (GetKeyState(VK_SHIFT))?'{':'[';
            break;
        case VK_OEM_5:
            c = (GetKeyState(VK_SHIFT))?'|':'\\';
            break;
		case VK_OEM_6:
            c = (GetKeyState(VK_SHIFT))?'}':']';
            break;
		case VK_OEM_7:
            c = (GetKeyState(VK_SHIFT))?'"':'\'';
            break;
		case VK_OEM_8:
            c = c;
            break;

        default://Let it translate to WM_CHAR
            return CDialog::PreTranslateMessage(pMsg);
        }

        if (pMsg->message == WM_KEYDOWN) {
            PostMessage(WM_COM_SEND_BYTE, (WPARAM)c);
        }
        return TRUE;
    } else if (pMsg->message == WM_CHAR) {
        PostMessage(WM_COM_SEND_BYTE, pMsg->wParam);
        return TRUE;
    }
    return CDialog::PreTranslateMessage(pMsg);
}

void CSimple232Dlg::OnDestroy()
{
    OnBnClickedButtonClose();
}

void CSimple232Dlg::OnBnClickedButtonOpen()
{
    // TODO: Add your control notification handler code here
    CString strComID;
    CString strBaud;
    CString strBits;
    CString strParity;
    CString strStopBits;
    int cursel;

    CString strFullDeviceName;
    char comIDValue[255] = {0};
    int baudValue = 0;
    int bitsValue = 0;
    int parityValue = 0;
    int stopBitsValue = 0;

    //Get serial device full name
    cursel = m_combox_id_.GetCurSel();
    m_combox_id_.GetLBText(cursel, strComID);
    memset(comIDValue, 0, sizeof(comIDValue));
    for (int i = 0;i < strComID.GetLength();i++) {
        char c = strComID.GetAt(i);
        if ((c != '|') && (c != ' ')) {
            comIDValue[i] = c;
        }
    }
    strFullDeviceName.Format("\\\\.\\%s", comIDValue);

    //Get baud
    cursel = m_combox_baud_.GetCurSel();
    m_combox_baud_.GetLBText(cursel, strBaud);
    sscanf(strBaud.GetBuffer(), "%d", &baudValue);

    //Get bits
    cursel = m_combox_bits_.GetCurSel();
    m_combox_bits_.GetLBText(cursel, strBits);
    sscanf(strBits.GetBuffer(), "%d", &bitsValue);

    //Get parity
    cursel = m_combox_parity_.GetCurSel();
    m_combox_parity_.GetLBText(cursel, strParity);
    parityValue = NOPARITY;
    for (int i = 0;i < sizeof(K_PARITY_STRING)/sizeof(const char*);i++) {
        if (strcmp(strParity.GetBuffer(), K_PARITY_STRING[i]) == 0) {
            parityValue = K_PARITY_ENUM[i];
            break;
        }
    }

    //Get stop bits
    cursel = m_combox_stopbits_.GetCurSel();
    m_combox_stopbits_.GetLBText(cursel, strStopBits);
    stopBitsValue = ONESTOPBIT;
    if (strncmp(strStopBits.GetBuffer(), "1.5", 3) == 0) {
        stopBitsValue = ONE5STOPBITS;
    } else if (strncmp(strStopBits.GetBuffer(), "1", 1) == 0) {
        stopBitsValue = ONESTOPBIT;
    } else if (strncmp(strStopBits.GetBuffer(), "2", 1) == 0) {
        stopBitsValue = TWOSTOPBITS;
    }


    //Open device
    _g_handleCOM = CreateFile(strFullDeviceName,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
        NULL);

    if (_g_handleCOM ==  INVALID_HANDLE_VALUE) {
        CString msg;
        msg.Format(" Error : Open %s\n", strFullDeviceName);
        AfxMessageBox(msg);
        return;
    }

    //Set serial config
    DCB dcb = {0};
    COMMTIMEOUTS TimeOuts;

    SetupComm(_g_handleCOM, 100, 100);

    TimeOuts.ReadIntervalTimeout        = 100;
    TimeOuts.ReadTotalTimeoutMultiplier = 0;
    TimeOuts.ReadTotalTimeoutConstant   = 0;

    TimeOuts.WriteTotalTimeoutMultiplier = 0;
    TimeOuts.WriteTotalTimeoutConstant   = 0;
    SetCommTimeouts(_g_handleCOM, &TimeOuts);

    //Init DCB and set
    memset(&dcb, 0, sizeof(dcb));
    GetCommState(_g_handleCOM, &dcb);

    dcb.DCBlength           = sizeof(dcb);
    dcb.BaudRate            = baudValue;
    dcb.ByteSize			= bitsValue;
    dcb.Parity			    = parityValue;
    dcb.StopBits			= stopBitsValue;

    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;

    dcb.fOutxCtsFlow = 0;
    dcb.fOutxDsrFlow = 0;

    if (!SetCommState(_g_handleCOM, &dcb)) {
        AfxMessageBox(" Error : Set dcb fail!\n");
        CloseHandle(_g_handleCOM);
        _g_handleCOM = INVALID_HANDLE_VALUE;
        return;
    }

    PurgeComm(_g_handleCOM, PURGE_TXCLEAR|PURGE_RXCLEAR);
    SetCommMask(_g_handleCOM, EV_RXCHAR);

    //Change GUI state
    m_combox_id_.EnableWindow(FALSE);
    m_combox_baud_.EnableWindow(FALSE);
    m_combox_bits_.EnableWindow(FALSE);
    m_combox_parity_.EnableWindow(FALSE);
    m_combox_stopbits_.EnableWindow(FALSE);

    m_button_open_.EnableWindow(FALSE);
    m_button_close_.EnableWindow(TRUE);
    m_listctrl_output_.SetFocus();

    //Prepare overlapped
    memset(&overlappedWrite_, 0, sizeof(OVERLAPPED));
    overlappedWrite_.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    //Reset
    receivedBytes_ = 0;
    sentBytes_ = 0;

    //Start back-end thread
    _g_handleExit = CreateEvent(NULL, FALSE, FALSE, NULL);
    AfxBeginThread((AFX_THREADPROC)_thread_receiver, this);
}

void CSimple232Dlg::OnBnClickedButtonClose()
{
    // TODO: Add your control notification handler code here
    SetEvent(_g_handleExit);
    Sleep(100);

    if (_g_handleCOM != INVALID_HANDLE_VALUE) {
        CloseHandle(_g_handleCOM);
        _g_handleCOM = INVALID_HANDLE_VALUE;
    } 

    if (_g_handleExit != INVALID_HANDLE_VALUE) {
        CloseHandle(_g_handleExit);
        _g_handleExit = INVALID_HANDLE_VALUE;
    }

    if (overlappedWrite_.hEvent != INVALID_HANDLE_VALUE) {
        CloseHandle(overlappedWrite_.hEvent);
        overlappedWrite_.hEvent = INVALID_HANDLE_VALUE;
    }

    m_combox_id_.EnableWindow(TRUE);
    m_combox_baud_.EnableWindow(TRUE);
    m_combox_bits_.EnableWindow(TRUE);
    m_combox_parity_.EnableWindow(TRUE);
    m_combox_stopbits_.EnableWindow(TRUE);

    m_button_open_.EnableWindow(TRUE);
    m_button_close_.EnableWindow(FALSE);
}

LRESULT CSimple232Dlg::OnComReceivedByte(WPARAM cbyte, LPARAM lparam)
{
    CString buffer;
    char c = (char)cbyte;

    int nr_rows = m_listctrl_output_.GetItemCount();

    //Remove oldest log.
    if (nr_rows > MAX_LOG_NUMBER) {
        m_listctrl_output_.DeleteItem(0);
        nr_rows--;
    }

    receivedBytes_++;
    PostMessage(WM_UPDATE_COUNTER);

    //ignore
    if (c == '\r') {
        return 0;
    }

    //new line
    if (c == '\n') {
        m_listctrl_output_.InsertItem(nr_rows, "");
        m_listctrl_output_.EnsureVisible(nr_rows, TRUE);
        return 0;
    }

    //inside a line, update string
    if (nr_rows > 0) {
        buffer = m_listctrl_output_.GetItemText(nr_rows-1, 0);
    }

    //make sure not
    CString strAppend;
    if ((c < 0) || isprint(c)) {//non english or valid printable char
        strAppend = CString(c);
    } else {
        strAppend.Format("\\x%02x", c & 0xFFu);
    }

    buffer += strAppend;

    //make sure wrap text.
    if (buffer.GetLength() <= 80) {
        m_listctrl_output_.SetItemText(nr_rows-1, 0, buffer);
        m_listctrl_output_.EnsureVisible(nr_rows-1, TRUE);
    } else {
        m_listctrl_output_.InsertItem(nr_rows, strAppend);
        m_listctrl_output_.EnsureVisible(nr_rows, TRUE);
    }

    return 0;
}

LRESULT CSimple232Dlg::OnComSendByte(WPARAM cbyte, LPARAM lparam)
{
    char buffer[1];
    buffer[0] = (char)cbyte;

    if (_g_handleCOM == INVALID_HANDLE_VALUE) {
        return 0;
    }

    DWORD dwSentBytes = 0;
    int rc = WriteFile(_g_handleCOM, buffer, 1, &dwSentBytes, &overlappedWrite_);

    if (!rc) {
        if (GetLastError() == ERROR_IO_PENDING) {
            while(!GetOverlappedResult(_g_handleCOM, &overlappedWrite_, &dwSentBytes, FALSE)) {
                if (GetLastError() == ERROR_IO_INCOMPLETE) {
                    continue;
                } else {
                    CString msg;
                    msg.Format("Error1 send byte 0x%08x", GetLastError());
                    AfxMessageBox(msg);
                    return 0;
                }
            }
        } else {
            CString msg;
            msg.Format("Error2 send byte 0x%08x", GetLastError());
            AfxMessageBox(msg);
            return 0;
        }
    }

    sentBytes_++;
    PostMessage(WM_UPDATE_COUNTER);

    return 0;
}

LRESULT CSimple232Dlg::OnUpdateCounter(WPARAM wparam, LPARAM lparam)
{
    CString msg;
    msg.Format("Sent %d bytes, Received %d bytes.", sentBytes_, receivedBytes_);
    m_edit_status_.SetWindowText(msg);
    return 0;
}
